/*
 *    Copyright 2022 The DSMS Authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.dsms.common.handler;

import cn.hutool.core.lang.Validator;
import com.dsms.common.constant.ResultCode;
import com.dsms.common.exception.DsmsEngineException;
import com.dsms.common.model.Result;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import io.swagger.annotations.ApiModelProperty;
import java.lang.reflect.Field;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.SocketException;
import java.sql.SQLException;

/**
 * Global exception handler
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {


    private static final int FRONT_MESSAGE_LENGTH_LIMIT = 100;

    /**
     * Handle the case where a message converter cannot read from an HTTP request
     */
    @ExceptionHandler(value = HttpMessageNotReadableException.class)
    public Result<Object> httpMessageNotReadableExceptionHandler(HttpMessageNotReadableException ex) {
        log.warn("[HttpMessageNotReadableException] error message:{}", ex.getMessage(), ex);
        String fieldErrorMessage = "非法的请求参数";
        if (ex.getCause() instanceof InvalidFormatException) {
            String fileName = "";
            try {
                InvalidFormatException invalidFormatException = (InvalidFormatException) ex.getCause();
                String fieldName = invalidFormatException.getPath().get(0).getFieldName();

                // use reflection to get the annotation information of the field
                Class<?> clazz = invalidFormatException.getPath().get(0).getFrom().getClass();
                Field field = clazz.getDeclaredField(fieldName);
                ApiModelProperty annotation = field.getAnnotation(ApiModelProperty.class);
                if (annotation != null) {
                    fileName = annotation.value();
                }
                //if converting the string to numbers throws an InvalidFormatException, even though the string is a valid number, display a front-end prompt indicating that the number exceeds the maximum range.
                if (Validator.isNumber((String) invalidFormatException.getValue()) && Number.class.isAssignableFrom(field.getType())) {
                    fieldErrorMessage = "超出数字最大范围";
                }
            } catch (Exception e) {
                log.warn("[HttpMessageNotReadableException] get field info error,error message:{}", e.getMessage(), e);
            }

            fieldErrorMessage = fileName + fieldErrorMessage;
        }
        return Result.normal(HttpStatus.OK.value(), ResultCode.BAD_REQUEST.getCode(), fieldErrorMessage, null);
    }

    /**
     * Handle the case when a required parameter is missing.
     */
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public Result<Object> missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) {
        log.warn("[MissingServletRequestParameterException handler] error message:{}", ex.getMessage(), ex);
        return Result.normal(HttpStatus.OK.value(), ResultCode.BAD_REQUEST.getCode(), ex.getMessage(), null);
    }

    /**
     * Handle the case where an argument annotated with {@code @Valid} such as an {@link RequestBody} or {@link RequestPart} argument fails validation
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Object> methodArgumentNotValidExceptionHandle(MethodArgumentNotValidException e) {
        log.warn("[MethodArgumentNotValidException handler] error message:{}", e.getMessage(), e);
        FieldError fieldError = e.getBindingResult().getFieldError();
        String fieldErrorMessage = "请求参数有误";
        if (!ObjectUtils.isEmpty(fieldError)) {
            fieldErrorMessage = fieldError.getDefaultMessage();
        }
        return Result.normal(HttpStatus.OK.value(), ResultCode.BAD_REQUEST.getCode(), fieldErrorMessage, null);
    }

    /**
     * Handle the case where no handler was found during the dispatch
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public Result<Object> noHandlerFoundExceptionHandler(HttpServletRequest req, NoHandlerFoundException ex) {
        log.warn("[NoHandlerFoundException handler] error message:{}", ex.getMessage(), ex);
        return Result.httpRes(ResultCode.NOT_FOUND);
    }


    /**
     * Handle the case where database access exceptions are thrown
     */
    @ExceptionHandler(value = SQLException.class)
    public Result<Object> sqlExceptionHandler(SQLException e, HttpServletResponse response) {
        log.warn("[sqlExceptionHandler handler] error message:{}", e.getMessage(), e);
        response.setStatus(HttpStatus.OK.value());
        return Result.error(ResultCode.DATABASE_ERROR);
    }

    /**
     * Handle the case where net exceptions are thrown
     */
    @ExceptionHandler(value = SocketException.class)
    public Result<Object> socketExceptionHandler(SocketException e, HttpServletResponse response) {
        log.warn("[socketExceptionHandler handler] error message:{}", e.getMessage(), e);
        response.setStatus(HttpStatus.OK.value());
        return Result.error(ResultCode.SOCKET_ERROR);
    }

    /**
     * Handle the case where custom dsms engine exceptions are thrown
     */
    @ExceptionHandler(value = DsmsEngineException.class)
    public Result<Object> dsmsEngineExceptionHandler(DsmsEngineException e, HttpServletResponse response) {
        log.warn("[DsmsEngineException handler] error message:{}", e.getMessage(), e);
        response.setStatus(HttpStatus.OK.value());
        return Result.normal(HttpStatus.OK.value(), e.getCode(), e.getMessage(), e.getData());
    }

    /**
     * Handle the case where other exceptions are thrown
     */
    @ExceptionHandler(value = Exception.class)
    public Result<Object> exceptionHandler(Exception e, HttpServletResponse response) {
        log.error("[Exception handler] error message:{}", e.getMessage(), e);
        if (e instanceof DsmsEngineException) {
            return dsmsEngineExceptionHandler((DsmsEngineException) e, response);
        }
        if (e instanceof SQLException) {
            return sqlExceptionHandler((SQLException) e, response);
        }
        if (e instanceof SocketException) {
            return socketExceptionHandler((SocketException) e, response);
        }

        if (!ObjectUtils.isEmpty(e.getMessage()) && e.getMessage().length() < FRONT_MESSAGE_LENGTH_LIMIT) {
            return Result.error(e.getMessage());
        } else {
            return Result.error(ResultCode.SERVER_ERROR);
        }
    }
}
